反射(Reflection),雖然是很久以前就已經有的概念,且在微軟的C#.NET中已經有做好相關的組件System.Reflection
,開發者可以很容易地去應用反射的概念,如果你想做下面的事情的話:
當我們自定義完一個類別(class),你要怎麼在執行階段取得類別的
或者當我們在Model裡面定義完一個方法,你要怎麼在執行階段取得方法的
只是不知怎麼的,待過的公司專案幾乎沒有用到這個概念,但它真的很好用,搭配泛型可以把很多東西提取出來共用,可以省下非常多的功夫。
最明顯可以寫成共用的就是ADO.NET,以前在使用ADO.NET存取資料庫的時候,不外乎就是SqlConnection連線之後,透過SqlCommand下指令,再透過SqlDataAdapter的Fill方法,把資料填入DataTable或是用DataReader來讀取資料再塞到Model裡面。
DataTable如果要轉換成Model List通常做法會是這樣。
//傳入DataTable物件,回傳Model List
public List<DataClass> GetModelList(DataTable dt)
{
var result = new List<DataClass>();
//一筆一筆透過欄位名稱與Model綁定
foreach(var row in dt.Rows)
{
var element = new DataClass();
element.PropA = row["PropA"].ToString();
element.PropB = row["PropB"].ToString();
//etc..
result.Add(element);
}
return result;
}
相當簡單易懂,Datable進來就一個一個綁定到Model裡面,非常適合新人來閱讀還有撰寫,不過寫久了就覺得感覺好流水帳,而且element.XXX = row.Column["XXX"].ToString(),這種動作欄位有幾個就要重複幾次,難道沒有更好的做法?有的,請看下方。
public List<DataClass> GetModelList(DataTable dt)
{
//一行解決
return dt.MapToModelList<DataClass>();
}
//DataTable與Model List轉換
//並應用泛型來使所有物件都可以使用這方法
public static List<T> MapToModelList<T>(this DataTable dataTable)
{
var result = new List<T>();
foreach (DataRow dr in dataTable.Rows)
{
result.Add(dr.MapToModel<T>());
}
return result;
}
//DataRow與Model轉換
private static T MapToModel<T>(this DataRow dataRow)
{
//先建立一個預設回傳值
var result = Activator.CreateInstance<T>();
//透過Reflection取得型別與屬性清單
foreach (var property in typeof(T).GetProperties())
{
//透過Reflection綁定值
property.SetValue(result, dataRow[property.Name].ToString());
}
return result;
}
感覺門檻高了一點,但是其實並不難,而且寫完之後可以打通你的任督二脈,之後如果有DataTable要轉Model List的,只要寫一行就好了。
類似作法也會出現在DataReader與Model List的轉換,比較傳統的作法會是這樣。
//傳入DataReader物件,回傳Model List
private static List<DataClass> GetModelList(DbDataReader dataReader)
{
var result = new List<DataClass>();
while (dataReader.Read())
{
var element = new DataClass();
element.PropA = dataReader["PropA"].ToString();
element.PropB = dataReader["PropB"].ToString();
//etc...
result.Add(element);
}
return result;
}
應用類似的概念,修改成
//DataReader與Model List的轉換
//並應用泛型來使所有物件都可以使用這方法
public List<T> MapToModelList<T>(DbDataReader dataReader)
{
var result = new List<T>();
while (dataReader.Read())
{
result.Add(dataReader.MapToModel<T>());
}
return result;
}
//DataReader與Model的轉換
private static T MapToModel<T>(this DbDataReader dataReader)
{
//先建立一個預設回傳值
var result = Activator.CreateInstance<T>();
//透過Reflection取得型別與屬性清單
foreach (var property in typeof(T).GetProperties())
{
//透過Reflection綁定值
property.SetValue(result, dataReader[property.Name]);
}
return result;
}
以上兩種做法都只是簡單實作一下,實際上直接呼叫這兩個方法會出現一些例外,例如資料庫欄位名稱不正確或者值的型態需要轉換等等,我有做了一個小小的套件放在Github上,這套件有解決了某些例外情形,例如屬性與欄位型別之間的判定以及如果屬性與欄位的名稱不同,可以透過Column Attribute來設定資料表欄位的名稱,希望提供給各位參考參考。
SimpleObjectMapper
應用反射的技巧,就可以省下很多時間來做更有意義的事情,但其實我這樣做的初衷很簡單,就是懶
,所以懶惰是人類進步的原動力,這個觀點真的是對極了。